package com.xjnt.frameworks.plugin.sql.criterion;

import java.io.Serializable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.common.collect.Lists;
import com.jfinal.kit.StrKit;
import com.xjnt.frameworks.core.BaseModel;
import com.xjnt.frameworks.plugin.sql.consts.FetchMode;
import com.xjnt.frameworks.plugin.sql.consts.KeyWord;
import com.xjnt.frameworks.plugin.sql.criterion.expression.Order;

public class SqlCriteria implements Serializable {

	private static final long serialVersionUID = 1L;

	private BaseModel<?> modelClass;
	private String alias;
	private List<Relation> relations = Lists.newArrayList();
	private List<Criterion> criterions = Lists.newArrayList();
	private List<Order> orders = Lists.newArrayList();
	private List<TypedValue> typedValues = Lists.newArrayList();

	public SqlCriteria(BaseModel<?> modelClass) {
		this(modelClass, null);
	}

	public SqlCriteria(BaseModel<?> modelClass, String alias) {
		this.modelClass = modelClass;
		this.alias = alias == null ? modelClass.getModelName() + "_" : alias;
	}

	public List<Relation> getRelations() {
		return relations;
	}

	public String toSelectSql(){
		StringBuilder sql = new StringBuilder();
		selectSql(sql);
		return sql.toString();
	}
	
	public String toConditionSql() {
		StringBuilder sql = new StringBuilder();
		fromSql(sql);
		combineSql(sql);
		return sql.toString();
	}

	public BaseModel<?> getModelClass() {
		return modelClass;
	}

	public String getAlias() {
		return alias;
	}

	public List<TypedValue> getTypedValues() {
		return typedValues;
	}

	public SqlCriteria addRelation(BaseModel<?> modelClass, String alias) {
		this.relations.add(new Relation(modelClass, alias));
		return this;
	}
	
	public SqlCriteria addRelation(BaseModel<?> modelClass, String alias, FetchMode fetchMode) {
		this.relations.add(new Relation(modelClass, alias, fetchMode));
		return this;
	}

	public SqlCriteria addRelation(String tableName, String alias) {
		this.relations.add(new Relation(tableName, alias));
		return this;
	}

	public SqlCriteria add(Criterion criterion) {
		criterions.add(criterion);
		return this;
	}

	public SqlCriteria addOrder(Order order) {
		orders.add(order);
		return this;
	}

	public boolean isExists(String columnName) {
		boolean flag = true;
		String[] cols = columnName.split(".");
		List<Relation> relations = getRelations();
		for (Relation relation : relations) {
			if (cols.length > 1) {
				if (relation.getAlias().equals(cols[0])) {
					if (relation.getModelClass() != null) {
						flag = relation.getModelClass().getTable()
								.hasColumnLabel(cols[1]);
					}
					break;
				}
			}
		}
		return flag;
	}
	
	public String toSql(){
		return toSelectSql() + toConditionSql();
	}

	private void selectSql(StringBuilder sql) {
		sql.append(KeyWord.SELECT.getValue()).append(" ");
		if (this.getModelClass() != null) {
			Map<String, Class<?>> columnTypeMap = this.modelClass.getTable().getColumnTypeMap();
			Set<String> keys = columnTypeMap.keySet();
			Iterator<String> itKeys = keys.iterator();
			for (int i = 0; itKeys.hasNext(); i++) {
				sql.append(getAlias()).append(".").append(itKeys.next());
				if (i < keys.size() - 1) {
					sql.append(", ");
				}
			}
		}
		for (int i = 0; i < relations.size(); i++) {
			Relation relation = relations.get(i);
			switch(relation.getFetchMode()){
				case FETCH:
					if (relation.getModelClass() != null){
						sql.append(", "); // 多表之间字段间隔符
						Map<String, Class<?>> columnTypeMap = relation.getModelClass().getTable().getColumnTypeMap();
						Set<String> keys = columnTypeMap.keySet();
						Iterator<String> itKeys = keys.iterator();
						for (int j = 0; itKeys.hasNext(); j++) {
							String fieldName = itKeys.next();
							String fieldAlias = relation.getModelClass().getModelName() + StrKit.firstCharToUpperCase(fieldName);
							if (relation.getAlias() != null) {
								sql.append(relation.getAlias()).append(".").append(fieldName).append(" ").append(KeyWord.AS).append(" ").append(fieldAlias);
							} else {
								sql.append(fieldName).append(" ").append(KeyWord.AS).append(" ").append(fieldAlias);
							}
							if (j < keys.size() - 1) {
								sql.append(", ");
							}
						}
					}
				default:
					break;
			}
		}
	}

	private void fromSql(StringBuilder sql) {
		sql.append(" ").append(KeyWord.FROM.getValue()).append(" ");
		sql.append(this.getModelClass().getTableName()).append(" ").append(KeyWord.AS).append(" ").append(this.getAlias());
		if(!getRelations().isEmpty()){
			sql.append(", ");
		}
		for (int i = 0; i < relations.size(); i++) {
			Relation relation = relations.get(i);
			if (relation.getModelClass() != null)
				sql.append(relation.getModelClass().getTableName());
			else
				sql.append(relation.getTableName());
			if (relation.getAlias() != null) {
				sql.append(" ").append(KeyWord.AS).append(" ").append(relation.getAlias());
			}
			if (i < relations.size() - 1) {
				sql.append(", ");
			}
		}
	}

	private void combineSql(StringBuilder sql) {
		if (!criterions.isEmpty()) {
			sql.append(" ").append(KeyWord.WHERE.getValue()).append(" ");
			for (int i = 0; i < criterions.size(); i++) {
				Criterion criterion = criterions.get(i);
				TypedValue[] typedVals = criterion.getTypedValues(this);
				for (TypedValue typedValue : typedVals) {
					typedValues.add(typedValue);
				}

				sql.append(criterion.toSql(this));
				if (i < criterions.size() - 1) {
					sql.append(" ").append(KeyWord.AND.getValue()).append(" ");
				}
			}
		}

		if (!orders.isEmpty()) {
			sql.append(" ").append(KeyWord.ORDERBY.getValue()).append(" ");
			for (int i = 0; i < orders.size(); i++) {
				if (i < orders.size() - 1) {
					sql.append(orders.get(i).toSql(this)).append(", ");
				} else {
					sql.append(orders.get(i).toSql(this));
				}
			}
		}
	}
}
